/*
 * Università degli Studi di Trento
 * Facoltà di Scienze MM.FF.NN.
 * Corso di Laurea Triennale in Informatica
 * A.A. 2010/2011
 * Progetto per il corso di Sistemi Operativi 1
 * Progetto 4: simulazione di una rotonda
 * Autori:
 *  Abram Lorenzo - 140368
 *  Dal Lago Loris - 139428
 *  Florio Alessandro - 140245
 */

/********************************************************************************
  Qui viene creata e gestita la rotatoria. Inoltre viene creata e inizializzata
  la FIFO che verra' usata per comunicare gli eventi alla telecamera
********************************************************************************/

#include "roundabout.h"
#include "driver.h"
#include "queue.h"

static int writeFifo;
static int readFifo;
#define fifoname "/tmp/cameraFifo"

/* Questi mutex vengono usati per evitare conflitti in caso di accessi simultanei
 * allo stesso settore della rotatoria */
static pthread_mutex_t mutexRoad[Baut_LEN];
/* Questi mutex invece servono per evitare operazioni simultanee sulle code di
 * accesso alla rotatoria */
static pthread_mutex_t queueMutex[4];

typedef struct SBaut {  
  ptrQueue entries[4];
  ptrCar road[Baut_LEN];
  ptrCar exits[4];
  bool isEmpty;
} Baut;

static void Baut_init(ptrBaut baut){
  int i;
  for (i = 0; i < 4; i++){
    baut->entries[i] = Queue_create();
    pthread_mutex_init(&queueMutex[i], NULL);
    baut->exits[i] = (ptrCar) NULL;
  }
  for (i = 0; i < Baut_LEN; i++){
    baut->road[i] = (ptrCar) NULL;
    pthread_mutex_init(&mutexRoad[i], NULL);
  }
  baut->isEmpty = true;

  /* se la creazione fallisce molto probabilmente è a causa di un file già
   * esistente, quindi lo provo ad eliminare e provo a ricreare la fifo */

  if (mkfifo(fifoname, 0664) < 0){
	  system("rm /tmp/cameraFifo");
  if (mkfifo(fifoname, 0664) < 0)
	  throw_error("Baut_init", "Impossibile creare la Fifo");
  }

  /* Apertura dei canali di scrittura e lettura della fifo */
  readFifo = open(fifoname, O_RDONLY | O_NONBLOCK);
  writeFifo = open(fifoname, O_WRONLY);

  if ((readFifo < 0) || (writeFifo < 0))
	  throw_error("Baut_init", "Errore nell'apertura della Fifo");

}

ptrBaut Baut_create(){
  ptrBaut baut = new(Baut);
  Baut_init(baut);
  return baut;
}

ptrCar Baut_getTopQueueCar(Place place, ptrBaut baut){
  ptrCar resCar = Queue_top(baut->entries[place]);
  return resCar;
}

static void Baut_enqueueCar(ptrCar car, Place place, ptrBaut baut){
  pthread_mutex_lock(&queueMutex[place]);
  Queue_enqueue(car, baut->entries[place]);
  pthread_mutex_unlock(&queueMutex[place]);
}

static void Baut_dequeueCar(Place place, ptrBaut baut){
  pthread_mutex_lock(&queueMutex[place]);
  Queue_dequeue(baut->entries[place]);
  pthread_mutex_unlock(&queueMutex[place]);
}

/* Metodo utilizzato dai thread per assicurarsi il lock su un settore
 * di rotatoria
 * la variabile baut e' passata come controllo di sicurezza */
pthread_mutex_t * Baut_getMutexRoad(ptrBaut baut){
	if (baut == NULL)
			throw_error("Baut_getMutexRoad", "baut e' vuota");
  return mutexRoad;
}

/* Metodo utilizzato dalla telecamera per accedere alla FIFO in lettura
 * Anche qui il parametro baut e' passato per sicurezza */
int Baut_getReadFifo(ptrBaut baut){
	if (baut == NULL)
				throw_error("Baut_getMutexRoad", "baut e' vuota");
	return readFifo;
}

static void Baut_checkEmpty(ptrBaut baut){
	int i;
	bool isEmpty = true;
	
	for (i = 0; i < 4 && isEmpty; i++){
	  isEmpty = isEmpty && (Queue_top(baut->entries[i]) == (ptrCar) NULL);
	}
	for (i = 0; i < Baut_LEN && isEmpty; i++){
		isEmpty = isEmpty && (baut->road[i] == (ptrCar) NULL);
	}
	for (i = 0; i < 4 && isEmpty; i++){
	  isEmpty = isEmpty && (baut->exits[i] == (ptrCar) NULL);
	}

	baut->isEmpty = isEmpty;
}

bool Baut_isEmpty(ptrBaut baut){
	return baut->isEmpty;
}


/* Questo metodo e' invocato per gestire i movimenti sulla rotatoria
 * e notificare alla telecamera quando avviene un evento */
void Baut_notify(ptrCar car, ptrBaut baut){

	ptrCar old_car = Car_copy(car);
	int event = Car_getEvent(old_car);

	switch(event){
	/* Arrivo di un'auto e inserimento della stessa nella coda della rotatoria appropriata
	 * Viene anche avviato il thread che gestira' l'auto */
  case ARRIVAL:
    {
      Place startPoint = Car_getStartPoint(car);
      Baut_enqueueCar(car, startPoint, baut);
      baut->isEmpty = false;

      struct toDrive_data * arg = new(struct toDrive_data);
      arg->car = car;
      arg->baut = baut;

      pthread_t pt;
      int rc = pthread_create(&pt, NULL, Driver_drive, (void *) arg);
      if (rc) throw_error_ends_with_int("main", "return code from pthread_create() is", rc);
    }
    break;
    /* Entrata di un auto in rotatoria */
  case ENTER:
    {
      Baut_dequeueCar(Car_getStartPoint(car), baut);
      int pos = Car_getPos(car);
      baut->road[pos] = car;
    }
    break;
    /* L'auto sta girando */
  case IN_SECTOR:
    {
      int prevPos = Car_getPrevPos(car);
      int pos = Car_getPos(car);
      baut->road[prevPos] = (ptrCar)NULL;
      baut->road[pos] = car;
    }
    break;
    /* L'auto e' arrivata all'uscita */
  case EXIT:
    {
      int destPoint = Car_getDestPoint(car);
      int prevPos = (destPoint*3+Baut_LEN-1)%Baut_LEN;
      baut->road[prevPos] = (ptrCar)NULL;
      baut->exits[destPoint] = car;
    }
    break;
    /* L'auto si allontana */
  case LEAVING:
    {
      baut->exits[Car_getDestPoint(car)] = (ptrCar)NULL;
      Baut_checkEmpty(baut);
    }
    break;
  default:
    throw_error("Baut_notify", "Evento non gestito!");
  }

	/* Inserimento in fifo della copia dell'auto per notificare
	 * l'evento alla telecamera senza preoccuparci di problemi
	 * di concorrenza */
	write(writeFifo, &old_car, sizeof(ptrCar));
}


#ifdef DEBUG
void Baut_printf (ptrBaut baut){
  /* BLA, BLA, BLA */
}
#endif /* DEBUG */

static void Baut_deinit(ptrBaut baut){
  /* Distruzione delle code e dei mutex */
  int i;
  for (i = 0; i < 4; i++){
    Queue_destroy(baut->entries[i]);
    pthread_mutex_destroy(&queueMutex[i]);
  }
  for (i = 0; i < Baut_LEN; i++){
    pthread_mutex_destroy(&mutexRoad[i]);
  }

  if (unlink(fifoname) < 0)
	  throw_error("Baut_deinit", "Impossibile distruggere la Fifo");
}

void Baut_destroy(ptrBaut baut){
  if (baut != NULL){
    Baut_deinit(baut);
    delete(baut);
  }
}
